/*
Copyright (C) 2011 The University of Michigan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Please send inquiries to powertutor@umich.edu
 */

package vn.cybersoft.obs.andriod.batterystats2.components;

import android.content.Context;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map;
import java.util.TreeMap;

import vn.cybersoft.obs.andriod.batterystats2.PowerNotifications;
import vn.cybersoft.obs.andriod.batterystats2.service.IterationData;
import vn.cybersoft.obs.andriod.batterystats2.service.PowerData;
import vn.cybersoft.obs.andriod.batterystats2.util.NotificationService;
import vn.cybersoft.obs.andriod.batterystats2.util.Recycler;

public class Sensors extends PowerComponent {
	private final String TAG = "Sensors";
	public static final int MAX_SENSORS = 10;

	public static class SensorData extends PowerData {
		private static Recycler<SensorData> recycler = new Recycler<SensorData>();

		public static SensorData obtain() {
			SensorData result = recycler.obtain();
			if(result != null) return result;
			return new SensorData();
		}

		@Override
		public void recycle() {
			recycler.recycle(this);
		}

		public double[] onTime;

		private SensorData() {
			onTime = new double[MAX_SENSORS];
		}

		public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
			StringBuilder res = new StringBuilder();
			for(int i = 0; i < MAX_SENSORS; i++) {
				if(onTime[i] > 1e-7) {
					res.append("Sensors-time ").append(i).append(" ")
					.append(onTime[i]).append("\n");
				}
			}
			out.write(res.toString());
		}
	}

	private Context context;
	private SensorManager sensorManager;
	private PowerNotifications sensorHook;

	private SensorStateKeeper sensorState;
	private SparseArray<SensorStateKeeper> uidStates;

	public Sensors(Context context) {
		this.context = context;
		sensorState = new SensorStateKeeper();
		uidStates = new SparseArray<SensorStateKeeper>();

		if(!NotificationService.available()) {
			Log.w(TAG, "Sensor component created although no notification service " +
					"available to receive sensor usage information");
			return;
		}
		sensorManager = (SensorManager)context.getSystemService(
				Context.SENSOR_SERVICE);
		sensorHook = new NotificationService.DefaultReceiver() {
			public void noteStartSensor(int uid, int sensor) {
				if(sensor < 0 || MAX_SENSORS <= sensor) {
					Log.w(TAG, "Received sensor outside of accepted range");
					return;
				}
				synchronized(sensorState) {
					sensorState.startSensor(sensor);
					SensorStateKeeper uidState = uidStates.get(uid);
					if(uidState == null) {
						uidState = new SensorStateKeeper();
						uidStates.put(uid, uidState);
					}
					uidState.startSensor(sensor);
				}
			}

			public void noteStopSensor(int uid, int sensor) {
				if(sensor < 0 || MAX_SENSORS <= sensor) {
					Log.w(TAG, "Received sensor outside of accepted range");
					return;
				}
				synchronized(sensorState) {
					sensorState.stopSensor(sensor);
					SensorStateKeeper uidState = uidStates.get(uid);
					if(uidState == null) {
						uidState = new SensorStateKeeper();
						uidStates.put(uid, uidState);
					}
					uidState.stopSensor(sensor);
				}
			}
		};
		NotificationService.addHook(sensorHook);
	}

	@Override
	protected void onExit() {
		super.onExit();
		NotificationService.removeHook(sensorHook);
	} 

	@Override
	public IterationData calculateIteration(long iteration) {
		IterationData result = IterationData.obtain();
		synchronized(sensorState) {
			SensorData globalData = SensorData.obtain();
			sensorState.setupSensorTimes(globalData.onTime, iterationInterval);
			result.setPowerData(globalData);

			for(int i = 0; i < uidStates.size(); i++) {
				int uid = uidStates.keyAt(i);
				SensorStateKeeper uidState = uidStates.valueAt(i);
				SensorData uidData = SensorData.obtain();
				uidState.setupSensorTimes(uidData.onTime, iterationInterval);
				result.addUidPowerData(uid, uidData);

				if(uidState.sensorsOn() == 0) {
					uidStates.remove(uid);
					i--;
				}
			}
		}
		return result;
	}

	private static class SensorStateKeeper {
		private int[] nesting;
		private long[] times;
		private long lastTime;
		private int count;

		public SensorStateKeeper() {
			nesting = new int[MAX_SENSORS];
			times = new long[MAX_SENSORS];
			lastTime = SystemClock.elapsedRealtime();
		}

		public void startSensor(int sensor) {
			if(nesting[sensor]++ == 0) {
				times[sensor] -= SystemClock.elapsedRealtime() - lastTime;
				count++;
			}
		}

		public void stopSensor(int sensor) {
			if(nesting[sensor] == 0) {
				return;
			} else if(--nesting[sensor] == 0) {
				times[sensor] += SystemClock.elapsedRealtime() - lastTime;
				count--;
			}
		}

		public int sensorsOn() {
			return count;
		}

		public void setupSensorTimes(double[] sensorTimes, long iterationInterval) {
			long now = SystemClock.elapsedRealtime();
			long div = now - lastTime;
			if(div <= 0) div = 1;
			for(int i = 0; i < MAX_SENSORS; i++) {
				sensorTimes[i] = 1.0 * (times[i] +
						(nesting[i] > 0 ? now - lastTime : 0)) / div;
				times[i] = 0;
			}
			lastTime = now;
		}
	}

	@Override
	public boolean hasUidInformation() {
		return true;
	}

	@Override
	public String getComponentName() {
		return "Sensors";
	}
}
